"""
LSP 客户端基类

提供与 Language Server Protocol 服务器通信的基础功能。
"""

import json
import subprocess
import threading
import uuid
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass


@dataclass
class LSPPosition:
    """LSP 位置 (line, character)"""
    line: int  # 0-based
    character: int  # 0-based


@dataclass
class LSPRange:
    """LSP 范围"""
    start: LSPPosition
    end: LSPPosition


@dataclass
class LSPLocation:
    """LSP 位置信息"""
    uri: str
    range: LSPRange


@dataclass
class LSPTextEdit:
    """LSP 文本编辑"""
    range: LSPRange
    new_text: str


@dataclass
class LSPWorkspaceEdit:
    """LSP 工作区编辑"""
    changes: Dict[str, List[LSPTextEdit]]  # URI -> edits


class LSPClient(ABC):
    """
    LSP 客户端基类

    实现 Language Server Protocol 的基本通信功能:
    - 启动和停止 LSP 服务器
    - 发送/接收 JSON-RPC 消息
    - 处理 initialize 握手
    - 提供常用 LSP 方法的封装
    """

    def __init__(self, workspace_path: str):
        """
        初始化 LSP 客户端

        Args:
            workspace_path: 工作区路径
        """
        self.workspace_path = Path(workspace_path).resolve()
        self.process: Optional[subprocess.Popen] = None
        self.initialized = False
        self.request_id = 0
        self.response_handlers: Dict[int, Any] = {}
        self._lock = threading.Lock()

    @abstractmethod
    def get_server_command(self) -> List[str]:
        """
        获取 LSP 服务器启动命令

        Returns:
            命令行参数列表
        """
        pass

    def start(self) -> bool:
        """
        启动 LSP 服务器

        Returns:
            是否启动成功
        """
        try:
            cmd = self.get_server_command()
            self.process = subprocess.Popen(
                cmd,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                cwd=str(self.workspace_path)
            )

            # 发送 initialize 请求
            if not self._initialize():
                self.stop()
                return False

            self.initialized = True
            return True

        except Exception as e:
            print(f"Failed to start LSP server: {e}")
            return False

    def stop(self):
        """停止 LSP 服务器"""
        if self.process:
            # 发送 shutdown 请求
            try:
                self._send_request("shutdown", {})
                self._send_notification("exit", {})
            except:
                pass

            # 终止进程
            self.process.terminate()
            try:
                self.process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                self.process.kill()

            self.process = None
            self.initialized = False

    def _get_next_id(self) -> int:
        """获取下一个请求 ID"""
        with self._lock:
            self.request_id += 1
            return self.request_id

    def _send_request(self, method: str, params: Dict) -> Optional[Dict]:
        """
        发送 JSON-RPC 请求并等待响应

        Args:
            method: LSP 方法名
            params: 参数

        Returns:
            响应结果，如果失败返回 None
        """
        if not self.process or not self.process.stdin:
            return None

        request_id = self._get_next_id()
        request = {
            "jsonrpc": "2.0",
            "id": request_id,
            "method": method,
            "params": params
        }

        try:
            # 发送请求
            content = json.dumps(request)
            message = f"Content-Length: {len(content)}\r\n\r\n{content}"
            self.process.stdin.write(message.encode('utf-8'))
            self.process.stdin.flush()

            # 读取响应
            return self._read_response()

        except Exception as e:
            print(f"Request failed: {e}")
            return None

    def _send_notification(self, method: str, params: Dict):
        """
        发送 JSON-RPC 通知 (无需响应)

        Args:
            method: LSP 方法名
            params: 参数
        """
        if not self.process or not self.process.stdin:
            return

        notification = {
            "jsonrpc": "2.0",
            "method": method,
            "params": params
        }

        try:
            content = json.dumps(notification)
            message = f"Content-Length: {len(content)}\r\n\r\n{content}"
            self.process.stdin.write(message.encode('utf-8'))
            self.process.stdin.flush()
        except Exception as e:
            print(f"Notification failed: {e}")

    def _read_response(self) -> Optional[Dict]:
        """
        读取 JSON-RPC 响应

        Returns:
            响应字典，如果失败返回 None
        """
        if not self.process or not self.process.stdout:
            return None

        try:
            # 读取 Content-Length 头
            header = b""
            while True:
                char = self.process.stdout.read(1)
                if not char:
                    return None
                header += char
                if header.endswith(b"\r\n\r\n"):
                    break

            # 解析 Content-Length
            header_str = header.decode('utf-8')
            content_length = 0
            for line in header_str.split('\r\n'):
                if line.startswith('Content-Length:'):
                    content_length = int(line.split(':')[1].strip())
                    break

            if content_length == 0:
                return None

            # 读取响应体
            content = self.process.stdout.read(content_length)
            response = json.loads(content.decode('utf-8'))

            return response

        except Exception as e:
            print(f"Failed to read response: {e}")
            return None

    def _initialize(self) -> bool:
        """
        发送 initialize 请求

        Returns:
            是否初始化成功
        """
        params = {
            "processId": None,
            "rootUri": self.workspace_path.as_uri(),
            "capabilities": {
                "textDocument": {
                    "rename": {
                        "prepareSupport": True
                    },
                    "references": {},
                    "definition": {}
                }
            }
        }

        response = self._send_request("initialize", params)
        if not response or "result" not in response:
            return False

        # 发送 initialized 通知
        self._send_notification("initialized", {})
        return True

    def open_document(self, file_path: str, content: Optional[str] = None) -> bool:
        """
        打开文档

        Args:
            file_path: 文件路径
            content: 文件内容，None 则从文件读取

        Returns:
            是否成功
        """
        if not self.initialized:
            return False

        file_path = Path(file_path).resolve()
        if content is None:
            try:
                content = file_path.read_text(encoding='utf-8')
            except Exception as e:
                print(f"Failed to read file: {e}")
                return False

        params = {
            "textDocument": {
                "uri": file_path.as_uri(),
                "languageId": self._get_language_id(),
                "version": 1,
                "text": content
            }
        }

        self._send_notification("textDocument/didOpen", params)
        return True

    def close_document(self, file_path: str):
        """
        关闭文档

        Args:
            file_path: 文件路径
        """
        file_path = Path(file_path).resolve()
        params = {
            "textDocument": {
                "uri": file_path.as_uri()
            }
        }
        self._send_notification("textDocument/didClose", params)

    @abstractmethod
    def _get_language_id(self) -> str:
        """
        获取语言 ID

        Returns:
            LSP 语言标识符
        """
        pass

    def rename(self, file_path: str, line: int, character: int, new_name: str) -> Optional[LSPWorkspaceEdit]:
        """
        重命名符号

        Args:
            file_path: 文件路径
            line: 行号 (0-based)
            character: 列号 (0-based)
            new_name: 新名称

        Returns:
            工作区编辑对象，如果失败返回 None
        """
        if not self.initialized:
            return None

        file_path = Path(file_path).resolve()
        params = {
            "textDocument": {
                "uri": file_path.as_uri()
            },
            "position": {
                "line": line,
                "character": character
            },
            "newName": new_name
        }

        response = self._send_request("textDocument/rename", params)
        if not response or "result" not in response:
            return None

        return self._parse_workspace_edit(response["result"])

    def find_references(self, file_path: str, line: int, character: int) -> List[LSPLocation]:
        """
        查找所有引用

        Args:
            file_path: 文件路径
            line: 行号 (0-based)
            character: 列号 (0-based)

        Returns:
            引用位置列表
        """
        if not self.initialized:
            return []

        file_path = Path(file_path).resolve()
        params = {
            "textDocument": {
                "uri": file_path.as_uri()
            },
            "position": {
                "line": line,
                "character": character
            },
            "context": {
                "includeDeclaration": True
            }
        }

        response = self._send_request("textDocument/references", params)
        if not response or "result" not in response:
            return []

        return self._parse_locations(response["result"])

    def find_definition(self, file_path: str, line: int, character: int) -> Optional[LSPLocation]:
        """
        查找定义

        Args:
            file_path: 文件路径
            line: 行号 (0-based)
            character: 列号 (0-based)

        Returns:
            定义位置，如果失败返回 None
        """
        if not self.initialized:
            return None

        file_path = Path(file_path).resolve()
        params = {
            "textDocument": {
                "uri": file_path.as_uri()
            },
            "position": {
                "line": line,
                "character": character
            }
        }

        response = self._send_request("textDocument/definition", params)
        if not response or "result" not in response:
            return None

        locations = self._parse_locations(response["result"])
        return locations[0] if locations else None

    def get_document_symbols(self, file_path: str) -> List[Dict]:
        """
        获取文档符号

        Args:
            file_path: 文件路径

        Returns:
            符号列表，每个符号包含 name, kind, range 等信息
        """
        if not self.initialized:
            return []

        file_path = Path(file_path).resolve()
        params = {
            "textDocument": {
                "uri": file_path.as_uri()
            }
        }

        response = self._send_request("textDocument/documentSymbol", params)
        if not response or "result" not in response:
            return []

        return response["result"] or []

    def find_symbol_by_name(self, file_path: str, symbol_name: str) -> Optional[tuple]:
        """
        通过名称查找符号位置

        Args:
            file_path: 文件路径
            symbol_name: 符号名称

        Returns:
            (line, character) 或 None
        """
        symbols = self.get_document_symbols(file_path)
        if not symbols:
            return None

        # 递归查找符号
        def search_symbols(symbol_list, name):
            for symbol in symbol_list:
                # 检查符号名称
                if symbol.get("name") == name:
                    range_data = symbol.get("range") or symbol.get("location", {}).get("range")
                    if range_data:
                        return (
                            range_data["start"]["line"],
                            range_data["start"]["character"]
                        )

                # 递归检查子符号
                children = symbol.get("children", [])
                if children:
                    result = search_symbols(children, name)
                    if result:
                        return result

            return None

        return search_symbols(symbols, symbol_name)

    def _parse_workspace_edit(self, edit_data: Dict) -> Optional[LSPWorkspaceEdit]:
        """解析 WorkspaceEdit"""
        if not edit_data or "changes" not in edit_data:
            return None

        changes = {}
        for uri, edits in edit_data["changes"].items():
            text_edits = []
            for edit in edits:
                text_edit = LSPTextEdit(
                    range=self._parse_range(edit["range"]),
                    new_text=edit["newText"]
                )
                text_edits.append(text_edit)
            changes[uri] = text_edits

        return LSPWorkspaceEdit(changes=changes)

    def _parse_locations(self, locations_data: Any) -> List[LSPLocation]:
        """解析 Location 列表"""
        if not locations_data:
            return []

        # 可能是单个 Location 或 Location 列表
        if isinstance(locations_data, dict):
            locations_data = [locations_data]

        locations = []
        for loc in locations_data:
            location = LSPLocation(
                uri=loc["uri"],
                range=self._parse_range(loc["range"])
            )
            locations.append(location)

        return locations

    def _parse_range(self, range_data: Dict) -> LSPRange:
        """解析 Range"""
        return LSPRange(
            start=LSPPosition(
                line=range_data["start"]["line"],
                character=range_data["start"]["character"]
            ),
            end=LSPPosition(
                line=range_data["end"]["line"],
                character=range_data["end"]["character"]
            )
        )

    def __enter__(self):
        """上下文管理器入口"""
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器退出"""
        self.stop()
